﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Drawing.Performance;

namespace Vortex.Drawing {

	//contains all text stuff for canvas
	public sealed partial class Canvas2D {
		#region TextLineInfo

		/// <summary>
		/// Contains information about text line
		/// </summary>
		private struct TextLineInfo {
			public int StartIndex;
			public int CharCount;
			public float LineWidth;

			/// <summary>
			/// Gets a value indicating whether this line is empty.
			/// </summary>
			/// <value><c>true</c> if this line is empty; otherwise, <c>false</c>.</value>
			public bool IsEmpty {
				get { return CharCount == 0; }
			}

			/// <summary>
			/// Sets this line info as the check point.
			/// </summary>
			/// <param name="info">The info with check point data.</param>
			public void SetCheckPoint(ref TextLineInfo info) {
				CharCount = info.CharCount;
				LineWidth = info.LineWidth;
			}
		}

		#endregion

		///<summary>Missing char code</summary>
		private static char _missingCharCode = '?';
		private static List<TextLineInfo> _textLines = new List<TextLineInfo>();

		/// <summary>
		/// Gets or sets the missing char code. It used for string drawing when actual char code is not fiund
		/// </summary>
		/// <value>The missing char code.</value>
		public static char MissingCharCode {
			get { return _missingCharCode; }
			set { _missingCharCode = value; }
		}

		/// <summary>
		/// Draws the text without formatting.
		/// </summary>
		/// <param name="font">The sprite font.</param>
		/// <param name="location">The location of left-top point of string.</param>
		/// <param name="text">The text to draw.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawText(SpriteFont font, Vector2 location, string text, ColorU colorTint) {
			DrawText(font, location, text, FontOptions.Default, colorTint);
		}

		/// <summary>
		/// Draws the text with additional font options.
		/// </summary>
		/// <param name="font">The sprite font.</param>
		/// <param name="location">The location of left-top point of string.</param>
		/// <param name="text">The text to draw.</param>
		/// <param name="options">The options.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawText(SpriteFont font, Vector2 location, string text, FontOptions options, ColorU colorTint) {
			if (null == font) throw new ArgumentNullException("font");
			if (null == text) throw new ArgumentNullException("text");

			Vector2 textCursor = location;

			for (int n = 0; n < text.Length; ++n) {
				char charCode = text[n];
				//check for new line constant
				if (charCode == '\n') {
					textCursor = new Vector2(location.X, textCursor.Y + font.LineHeight + options.LineSpacing);
					continue;
				}

				SpriteCharInfo charInfo = font[charCode] ?? font[_missingCharCode];
				if (null != charInfo) {
					DrawSprite(textCursor, charInfo.Glyph, colorTint);
					textCursor.X += charInfo.Size.X + options.LetterSpacing;
				}

#if PERF_STATS
				_device.PerfMonitor.AddCounter(DeviceCounter.TextCharDrawn);
#endif
			}
		}

		/// <summary>
		/// Measures the given text. 
		/// </summary>
		/// <param name="font">The font.</param>
		/// <param name="text">The text.</param>
		/// <returns>Size of given string on screen.</returns>
		public static Vector2 MeasureText(SpriteFont font, string text) {
			return MeasureText(font, text, FontOptions.Default);
		}

		/// <summary>
		/// Measures the given text.
		/// </summary>
		/// <param name="font">The font.</param>
		/// <param name="text">The text.</param>
		/// <param name="options">Additional font options.</param>
		/// <returns>Size of given string on screen.</returns>
		public static Vector2 MeasureText(SpriteFont font, string text, FontOptions options) {
			if (null == font) throw new ArgumentNullException("font");
			if (null == text) throw new ArgumentNullException("text");
			if (null == options) throw new ArgumentNullException("options");

			if (text.Length == 0) {
				return Vector2.Zero;
			}

			Vector2 textCursor = new Vector2(0, font.LineHeight);
			Vector2 textSize = Vector2.Zero;

			for (int n = 0; n < text.Length; ++n) {
				char charCode = text[n];
				//check for new line constant
				if (charCode == '\n') {
					textCursor.X = 0;
					textCursor.Y += font.LineHeight + options.LineSpacing;					
					continue;
				}

				SpriteCharInfo charInfo = font[charCode] ?? font[_missingCharCode];
				if (null != charInfo) {
					textCursor.X += charInfo.Size.X + options.LetterSpacing;
					Vector2.Max(ref textCursor, ref textSize, out textSize);
				}
			}

			return textSize;
		}

		/// <summary>
		/// Measures the text bounding rect.
		/// </summary>
		/// <param name="font">The font to use.</param>
		/// <param name="location">The location of string rect left-top point.</param>
		/// <param name="text">The text to measure.</param>
		/// <returns>Text bounding rectangle</returns>
		public static Rect MeasureTextBounds(SpriteFont font, Vector2 location, string text) {
			return MeasureTextBounds(font, location, text, FontOptions.Default);
		}

		/// <summary>
		/// Measures the text bounding rect.
		/// </summary>
		/// <param name="font">The font to use.</param>
		/// <param name="location">The location of string rect left-top point.</param>
		/// <param name="text">The text to measure.</param>
		/// <param name="options">Additional font options.</param>
		/// <returns>Text bounding rectangle</returns>
		public static Rect MeasureTextBounds(SpriteFont font, Vector2 location, string text, FontOptions options) {
			return Rect.FromBox(location, MeasureText(font, text, options));
		}

		/// <summary>
		/// Draws the text in bounding rectangular region with specified layout.
		/// </summary>
		/// <param name="font">The font.</param>
		/// <param name="region">The bounding region.</param>
		/// <param name="text">The text to draw.</param>
		/// <param name="layout">The layout to use.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawTextLayout(SpriteFont font, Rect region, string text, TextLayout layout, ColorU colorTint) {
			DrawTextLayout(font, region, text, layout, FontOptions.Default, colorTint);
		}

		/// <summary>
		/// Draws the text in bounding rectangular region with specified layout.
		/// </summary>
		/// <param name="font">The font.</param>
		/// <param name="region">The bounding region.</param>
		/// <param name="text">The text to draw.</param>
		/// <param name="layout">The layout to use.</param>
		/// <param name="options">Additional font options for specifying spacings or so.</param>
		/// <param name="colorTint">The color tint.</param>
		public void DrawTextLayout(SpriteFont font, Rect region, string text, TextLayout layout, FontOptions options, ColorU colorTint) {
			if (null == font) throw new ArgumentNullException("font");
			if (null == text) throw new ArgumentNullException("text");
			if (null == options) throw new ArgumentNullException("options");

			if (BreakTextIntoLines(font, text, region.Width, options.LetterSpacing)) {
				float lineHeight = font.LineHeight + options.LineSpacing;
				//now we can start determine, how many lines can we fit?
				int lineCount = Math.Min((int)Math.Floor(region.Height / lineHeight), _textLines.Count);
				
				float visibleHeight = lineCount * lineHeight, offsetTop = region.Top;

				if ((layout & TextLayout.Bottom) == TextLayout.Bottom) {
					offsetTop +=(float)Math.Floor( (region.Height - visibleHeight) * (((layout & TextLayout.Top) == TextLayout.Top) ? 0.5f : 1));
				}

				for (int n = 0; n < lineCount; ++n) {
					TextLineInfo lineInfo = _textLines[n];

					if (!lineInfo.IsEmpty) {
						float offsetLeft = region.Left;
						if ((layout & TextLayout.Right) == TextLayout.Right) {
							offsetLeft += (float)Math.Floor((region.Width - lineInfo.LineWidth) * (((layout & TextLayout.Left) == TextLayout.Left) ? 0.5f : 1));
						}

						DrawText(font, new Vector2(offsetLeft, offsetTop).Floored, text.Substring(lineInfo.StartIndex, lineInfo.CharCount), options, colorTint);
					}

					offsetTop += lineHeight;
				}				
			}
		}

		/// <summary>
		/// Breaks the text into lines.
		/// </summary>
		/// <param name="font">The font.</param>
		/// <param name="text">The text.</param>
		/// <param name="maxLineWidth">Width of the bounding rect.</param>
		/// <param name="xScaling">The x scaling.</param>
		/// <param name="xSpacing">The x spacing.</param>
		/// <returns></returns>
		private static bool BreakTextIntoLines(SpriteFont font, string text, float maxLineWidth, float xSpacing) {
			_textLines.Clear();

			TextLineInfo currentPoint = new TextLineInfo();
			TextLineInfo checkPoint = new TextLineInfo();

			for (int n = 0; n < text.Length; ) {
				char ch = text[n];

				if (ch == '\n') {
					checkPoint.SetCheckPoint(ref currentPoint);
					_textLines.Add(checkPoint);

					currentPoint = checkPoint = new TextLineInfo();
					checkPoint.StartIndex = ++n;
					continue;
				}

				if (ch == ' ' && !currentPoint.IsEmpty) {
					checkPoint.SetCheckPoint(ref currentPoint);
				}

				//retrieve character
				SpriteCharInfo charInfo = font[ch] ?? font[MissingCharCode];
				float charWidth = charInfo.Size.X + xSpacing;

				if (maxLineWidth < currentPoint.LineWidth + charWidth) {
					//break immediately when single char is bigger than available line
					if (maxLineWidth < charWidth) {
						return false;
					}
					//check is it required to add immediate checkpoint
					if (checkPoint.IsEmpty) {
						checkPoint.SetCheckPoint(ref currentPoint);
					} 
					//add line to list if so
					_textLines.Add(checkPoint);
					//set next point to draw
					n = checkPoint.StartIndex + checkPoint.CharCount;
					n += (text[n] == ' ') ? 1 : 0;
					//reset all
					currentPoint = checkPoint = new TextLineInfo();
					checkPoint.StartIndex = n;
					continue;
				} else {
					currentPoint.LineWidth += charWidth;
					currentPoint.CharCount++;
					n++;
				}
			}
			if (!currentPoint.IsEmpty) {
				checkPoint.SetCheckPoint(ref currentPoint);
				_textLines.Add(checkPoint);
			}
			return true;
		}

	}
}
